package com.snail.oauth;

import com.tangjiu.auth.JwtAuthenticationTokenFilter;
import com.tangjiu.auth.MyUserDetailsService;
import com.tangjiu.auth.security.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
 * @autor houyanfeng 2021/3/4
 */
@EnableWebSecurity
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailsService myUserDetailsService;
    @Autowired
    private AjaxAccessDeniedHandler ajaxAccessDeniedHandler;//无权访问返回
    @Autowired
    private AjaxloginSuccessHandler ajaxloginSuccessHandler;
    @Autowired
    private AjaxAuthenticationEntryPoint authenticationEntryPoint;//未登陆时返回
    @Autowired
    private AjaxLogoutSuccessHandler logoutSuccessHandler;//注销成功返回
    @Autowired
    private AjaxAuthenticationFailureHandler authenticationFailureHandler;//登录失败
    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; // JWT 拦截器

    @Autowired
    private MySecurityMetadataSource mySecurityMetadataSource;
    @Autowired
    private MyAccessDecisionManager myAccessDecisionManager;
    @Autowired
    private DataSource dataSource;

    /**
     * 配置userDetails的数据源，密码加密格式
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //BCryptPasswordEncoder也是目前比较流行安全的一种加密方式，它比MD5效率更高
        auth.userDetailsService(myUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    /** 配置放行的资源 */
    @Override
    public void configure(WebSecurity web) throws Exception {
        //过滤swagger
        web.ignoring().antMatchers("/swagger-ui.html")
                .antMatchers("/webjars/**")
                .antMatchers("/v2/**")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/static/**")
                .antMatchers("/sync/openapi/hd31/**");
        //对于在header里面增加token等类似情况，放行所有OPTIONS请求。
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().disable().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) /*基于token，所以不需要session*/
                .and()
                .authorizeRequests()//定义哪些URL需要被保护、哪些不需要被保护
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(mySecurityMetadataSource);
                        object.setAccessDecisionManager(myAccessDecisionManager);
                        return object;
                    }
                })
                // 登录url不验证
                .antMatchers("/login").permitAll()
                // 剩下所有请求都需要认证
                .anyRequest().authenticated()
                .and()
                .formLogin()  //使用自带登陆
                .successHandler(ajaxloginSuccessHandler) // 登录成功
                .failureHandler(authenticationFailureHandler) // 登录失败
                .and()

                .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(logoutSuccessHandler);
        // 记住我
        http.rememberMe().rememberMeParameter("rememberMe")
                //tokenValiditySeconds()配置cookie的过期时间，默认就是1209600秒，即2周。
                .userDetailsService(myUserDetailsService).tokenRepository(tokenRepository()).tokenValiditySeconds(1209600);
        http.exceptionHandling()// 权限处理信息
                .accessDeniedHandler(ajaxAccessDeniedHandler) // 无权限资源时的异常
                .authenticationEntryPoint(authenticationEntryPoint);//匿名用户访问无权限资源时的异常
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);// 添加自定义 JWT 过滤器
    }

    /**
     * 重写，解决在login中无法注入的问题
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PersistentTokenRepository tokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepositoryImpl = new JdbcTokenRepositoryImpl();
        jdbcTokenRepositoryImpl.setDataSource(dataSource);
        //自动创建数据库表:persistent_logins，使用一次后注释掉，不然会报错
        //jdbcTokenRepositoryImpl.setCreateTableOnStartup(true);
        return jdbcTokenRepositoryImpl;
    }
}
